#pragma once

/*
 * Slim ELF file parser
 * Dependencies: bzbfile, elfparser.cpp
 */


#include <cstdint>
#include <string>
#include <vector>


namespace bzbfile {


// Magic number 7F 45 4c 46
const uint8_t ELF_EI_MAG0 = 0x00;
const uint8_t ELF_EI_MAG1 = 0x01;
const uint8_t ELF_EI_MAG2 = 0x02;
const uint8_t ELF_EI_MAG3 = 0x03;
const uint8_t ELF_EI_CLASS = 0x04;
const uint8_t ELF_EI_DATA = 0x05;
const uint8_t ELF_EI_VERSION = 0x06;
const uint8_t ELF_EI_OSABI = 0x07;
const uint8_t ELF_EI_ABIVERSION = 0x08;
const uint8_t ELF_EI_PAD = 0x09;


// Reference values
const uint8_t ELF_CLASS_32 = 1;
const uint8_t ELF_CLASS_64 = 2;

const uint8_t ELF_LITTLE_ENDIAN = 1;
const uint8_t ELF_BIG_ENDIAN = 2;

const uint8_t ELF_VERSION_ORIGINAL = 1;

const uint32_t ELF_EV_NONE = 0;
const uint32_t ELF_EV_CURRENT = 1;

// NOTE: ELF_EI_OSABI is often set to 0x00 on other platforms
const uint8_t ELF_ABI_SYSTEM_V = 0x00;
const uint8_t ELF_ABI_HP_UX = 0x01;
const uint8_t ELF_ABI_NETBSD = 0x02;
const uint8_t ELF_ABI_LINUX = 0x03;
const uint8_t ELF_ABI_GNUHURD = 0x04;
const uint8_t ELF_ABI_SOLARIS = 0x06;
const uint8_t ELF_ABI_AIX = 0x07;
const uint8_t ELF_ABI_IRIX = 0x08;
const uint8_t ELF_ABI_FREEBSD = 0x09;
const uint8_t ELF_ABI_TRU64 = 0x0A;
const uint8_t ELF_ABI_NOVELL_MODESTO = 0x0B;
const uint8_t ELF_ABI_OPENBSD = 0x0C;
const uint8_t ELF_ABI_OPENVMS = 0x0D;
const uint8_t ELF_ABI_NONSTOP_KERNEL = 0x0E;
const uint8_t ELF_ABI_AROS = 0x0F;
const uint8_t ELF_ABI_FENIXOS = 0x10;
const uint8_t ELF_ABI_CLOUDABI = 0x11;

const uint16_t ELF_ET_NONE = 0x00;
const uint16_t ELF_ET_REL = 0x01;
const uint16_t ELF_ET_EXEC = 0x02;
const uint16_t ELF_ET_DYN = 0x03;
const uint16_t ELF_ET_CORE = 0x04;
const uint16_t ELF_ET_LOOS = 0xfe00;
const uint16_t ELF_ET_HIOS = 0xfeff;
const uint16_t ELF_ET_LOPROC = 0xff00;
const uint16_t ELF_ET_HIPROC = 0xffff;

const uint16_t ELF_ARCH_UNSPECIFIED = 0x00;
const uint16_t ELF_ARCH_SPARC = 0x01;
const uint16_t ELF_ARCH_X86 = 0x03;
const uint16_t ELF_ARCH_MIPS = 0x08;
const uint16_t ELF_ARCH_POWERPC = 0x14;
const uint16_t ELF_ARCH_S390 = 0x16;
const uint16_t ELF_ARCH_ARM = 0x28;
const uint16_t ELF_ARCH_SUPERH = 0x2A;
const uint16_t ELF_ARCH_IA_64 = 0x32;
const uint16_t ELF_ARCH_X86_64 = 0x3E;
const uint16_t ELF_ARCH_AARCH64 = 0xB7;
const uint16_t ELF_ARCH_RISC_V = 0xF3;

const uint32_t ELF_PT_NULL = 0x00000000;
const uint32_t ELF_PT_LOAD = 0x00000001;
const uint32_t ELF_PT_DYNAMIC = 0x00000002;
const uint32_t ELF_PT_INTERP = 0x00000003;
const uint32_t ELF_PT_NOTE = 0x00000004;
const uint32_t ELF_PT_SHLIB = 0x00000005;
const uint32_t ELF_PT_PHDR = 0x00000006;
const uint32_t ELF_PT_LOOS = 0x60000000;
const uint32_t ELF_PT_HIOS = 0x6FFFFFFF;
const uint32_t ELF_PT_LOPROC = 0x70000000;
const uint32_t ELF_PT_HIPROC = 0x7FFFFFFF;
const uint32_t ELF_PT_GNU_EH_FRAME = 0x6474e550;
const uint32_t ELF_PT_GNU_STACK	= /*(ELF_PT_LOOS + 0x474e551)*/0x6474e551;
const uint32_t ELF_PT_GNU_RELRO = 0x6474e552;

const uint32_t ELF_PH_FLAG_EXEC = 0x00000001;
const uint32_t ELF_PH_FLAG_WRITE = 0x00000002;
const uint32_t ELF_PH_FLAG_READ = 0x00000004;

const uint32_t ELF_SHT_NULL = 0x0; // Section header table entry unused
const uint32_t ELF_SHT_PROGBITS = 0x1; // Program data
const uint32_t ELF_SHT_SYMTAB = 0x2; // Symbol table
const uint32_t ELF_SHT_STRTAB = 0x3; // String table
const uint32_t ELF_SHT_RELA = 0x4; // Relocation entries with addends
const uint32_t ELF_SHT_HASH = 0x5; // Symbol hash table
const uint32_t ELF_SHT_DYNAMIC = 0x6; // Dynamic linking information
const uint32_t ELF_SHT_NOTE = 0x7; // Notes
const uint32_t ELF_SHT_NOBITS = 0x8; // Program space with no data (bss)
const uint32_t ELF_SHT_REL = 0x9; // Relocation entries, no addends
const uint32_t ELF_SHT_SHLIB = 0x0A; // Reserved
const uint32_t ELF_SHT_DYNSYM = 0x0B; // Dynamic linker symbol table
const uint32_t ELF_SHT_INIT_ARRAY = 0x0E; // Array of constructors
const uint32_t ELF_SHT_FINI_ARRAY = 0x0F; // Array of destructors
const uint32_t ELF_SHT_PREINIT_ARRAY = 0x10; // Array of pre-constructors
const uint32_t ELF_SHT_GROUP = 0x11; // Section group
const uint32_t ELF_SHT_SYMTAB_SHNDX = 0x12; // Extended section indices
const uint32_t ELF_SHT_NUM = 0x13; // Number of defined types.
const uint32_t ELF_SHT_LOOS = 0x60000000;
const uint32_t ELF_SHT_HIOS = 0x6fffffff;
const uint32_t ELF_SHT_LOPROC = 0x70000000;
const uint32_t ELF_SHT_HIPROC = 0x7fffffff;
const uint32_t ELF_SHT_LOUSER = 0x80000000;
const uint32_t ELF_SHT_HIUSER = 0xffffffff;
const uint32_t ELF_SHT_GNU_HASH = 0x6ffffff6;
const uint32_t ELF_SHT_VERNEED = 0x6ffffffe;

const uint32_t ELF_SHF_WRITE = 0x1;
const uint32_t ELF_SHF_ALLOC = 0x2;
const uint32_t ELF_SHF_EXECINSTR = 0x4;
const uint32_t ELF_SHF_MERGE = 0x10;
const uint32_t ELF_SHF_STRINGS = 0x20;
const uint32_t ELF_SHF_INFO_LINK = 0x40;
const uint32_t ELF_SHF_LINK_ORDER = 0x80;
const uint32_t ELF_SHF_OS_NONCONFORMING = 0x100;
const uint32_t ELF_SHF_GROUP = 0x200;
const uint32_t ELF_SHF_TLS = 0x400;
const uint32_t ELF_SHF_COMPRESSED = 0x800;
const uint32_t ELF_SHF_MASKOS = 0x0ff00000;
const uint32_t ELF_SHF_MASKPROC = 0xf0000000;

struct ELF32;
struct ELF64;


/*
 * Returns an ELF file's class (32 or 64 bit)
 * You can use ELF_CLASS_32 and ELF_CLASS_64 to compare
 */

uint8_t get_elf_class(const std::string &filename);


/*
 * Parses a 32-bit ELF file
 */
ELF32 parse_elf32(const std::string &filename);


/*
 * Parses a 64-bit ELF file
 */
ELF64 parse_elf64(const std::string &filename);


struct PROGHEADER32 {
	uint32_t p_type;
	uint32_t p_offset;
	uint32_t p_vaddr;
	uint32_t p_paddr;
	uint32_t p_filesz;
	uint32_t p_memsz;
	uint32_t p_flags;
	uint32_t p_align;
};


struct PROGHEADER64 {
	uint32_t p_type;
	uint32_t p_flags;
	uint64_t p_offset;
	uint64_t p_vaddr;
	uint64_t p_paddr;
	uint64_t p_filesz;
	uint64_t p_memsz;
	uint64_t p_align;
};


struct SECHEADER32 {
	std::string name {""};
	uint32_t sh_name;
	uint32_t sh_type;
	uint32_t sh_flags;
	uint32_t sh_addr;
	uint32_t sh_offset;
	uint32_t sh_size;
	uint32_t sh_link;
	uint32_t sh_info;
	uint32_t sh_addralign;
	uint32_t sh_entsize;
};


struct SECHEADER64 {
	std::string name {""};
	uint32_t sh_name;
	uint32_t sh_type;
	uint64_t sh_flags;
	uint64_t sh_addr;
	uint64_t sh_offset;
	uint64_t sh_size;
	uint32_t sh_link;
	uint32_t sh_info;
	uint64_t sh_addralign;
	uint64_t sh_entsize;
};


struct ELF32 {
	uint8_t e_ident[16];
	uint16_t e_type;		// Type of file
	uint16_t e_machine;		// Target architecture
	uint32_t e_version;		// ELF version
	uint32_t e_entry;		// Entry point
	uint32_t e_phoff;		// Program header offset
	uint32_t e_shoff;		// Section header offset
	uint32_t e_flags;		// Flags
	uint16_t e_ehsize;		// Header size
	uint16_t e_phentsize;	// Program header entry size
	uint16_t e_phnum;		// Number of program headers
	uint16_t e_shentsize;	// Section header entry size
	uint16_t e_shnum;		// Number of section headers
	uint16_t e_shstrndx;	// Section headers table index

	std::vector<PROGHEADER32> prog_headers;
	std::vector<SECHEADER32> sec_headers;
	std::string interpreter {""};
	std::string filename {""};

	ELF32(std::string filename) {
		*this = parse_elf32(filename);
	}

	void read(std::string filename) {
		*this = parse_elf32(filename);
	}

	ELF32() {}
};


struct ELF64 {
	uint8_t e_ident[16];
	uint16_t e_type;
	uint16_t e_machine;
	uint32_t e_version;
	uint64_t e_entry;
	uint64_t e_phoff;
	uint64_t e_shoff;
	uint32_t e_flags;
	uint16_t e_ehsize;
	uint16_t e_phentsize;
	uint16_t e_phnum;
	uint16_t e_shentsize;
	uint16_t e_shnum;
	uint16_t e_shstrndx;

	std::vector<PROGHEADER64> prog_headers;
	std::vector<SECHEADER64> sec_headers;
	std::string interpreter {""};
	std::string filename {""};

	ELF64(std::string filename) {
		*this = parse_elf64(filename);
	}

	void read(std::string filename) {
		*this = parse_elf64(filename);
	}

	ELF64() {}
};


}
